/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 * Description: MMKV for Cangjie API
 * Author: xwx1230846
 * Create: 2023-04-12
 * Notes: 2023-04-12 Create
 */

#include "native-mmkv.h"
#include "MMKVPredef.h"
#include "MMBuffer.h"
#include "MMKV.h"
#include "MMKVLog.h"
#include "MemoryFile.h"
#include <cstdint>
#include <string>

#ifdef __cplusplus
extern "C" {
#endif

using namespace std;
using namespace mmkv;

static MMKVHandler gCallbackHandler;

#ifdef _WIN32
static wchar_t *char2wchar(const char *cchar)
{
    wchar_t *m_wchar = nullptr;
    int len = MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), NULL, 0);
    if (len >= 0) {
        m_wchar= new wchar_t[len + 1];
    }
    MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), m_wchar, len);
    m_wchar[len]= '\0';
    return m_wchar;
}
#endif

int64_t getSizeofSize_t()
{
    return sizeof(size_t);
}

static void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message)
{
    if (gCallbackHandler.logHandler) {
        gCallbackHandler.logHandler(level, file, line, function, message.c_str());
    }
}

static MMKVRecoverStrategic onMMKVError(const std::string &mmapID, MMKVErrorType errorType)
{
    if (errorType == MMKVCRCCheckFail) {
        if (gCallbackHandler.onMMKVCRCCheckFail) {
            MMKVRecoverStrategic ret = gCallbackHandler.onMMKVCRCCheckFail(mmapID.c_str());
            return ret;
        }
    } else {
        if (gCallbackHandler.onMMKVFileLengthError) {
            MMKVRecoverStrategic ret = gCallbackHandler.onMMKVFileLengthError(mmapID.c_str());
            return ret;
        }
    }
    return OnErrorDiscard;
}

static void onMMKVContentChange(const string &mmapID)
{
    if (gCallbackHandler.onMMKVContentChange) {
        gCallbackHandler.onMMKVContentChange(mmapID.c_str());
    }
}

void initializeMMKV_c(char *rootDir, int logLevel, MMKVHandler handler)
{
#ifdef _WIN32
    wstring strRootDir(char2wchar(rootDir));
#else
    string strRootDir(rootDir);
#endif
    gCallbackHandler = handler;
    if (gCallbackHandler.logHandler) {
        MMKV::initializeMMKV(strRootDir, (MMKVLogLevel)logLevel, mmkvLog);
    } else {
        MMKV::initializeMMKV(strRootDir, (MMKVLogLevel)logLevel, nullptr);
    }

    if (gCallbackHandler.onMMKVCRCCheckFail || gCallbackHandler.onMMKVFileLengthError) {
        MMKV::registerErrorHandler(onMMKVError);
    }

    if (gCallbackHandler.onMMKVContentChange) {
        MMKV::registerContentChangeHandler(onMMKVContentChange);
    }
}

void onExit_c()
{
    MMKV::onExit();
}

MMKV *defaultMMKV_c(MMKVMode mode, char *cryptKey)
{
    MMKV *kv = nullptr;

    if (cryptKey != NULL) {
        if (strlen(cryptKey) > 0) {
            string strCryptKey(cryptKey);
            kv = MMKV::defaultMMKV(mode, &strCryptKey);
        }
    }

    if (!kv) {
        kv = MMKV::defaultMMKV((MMKVMode) mode, nullptr);
    }

    return kv;
}

MMKV *mmkvWithID_c(char *mmapID, MMKVMode mode, char *cryptKey, char *rootPath)
{
    MMKV *kv = nullptr;
    if (!mmapID) {
        return kv;
    }

    string strMmapID(mmapID);

    if (cryptKey && strlen(cryptKey) > 0) {
        string strCryptKey(cryptKey);
        if (rootPath) {
#ifdef _WIN32
            wstring strRootPath(char2wchar(rootPath));
#else
            string strRootPath(rootPath);
#endif
            kv = MMKV::mmkvWithID(strMmapID, (MMKVMode)mode, &strCryptKey, &strRootPath);
        } else {
            kv = MMKV::mmkvWithID(strMmapID, (MMKVMode)mode, &strCryptKey, nullptr);
        }
    } else {
        if (rootPath) {
#ifdef _WIN32
            wstring strRootPath(char2wchar(rootPath));
#else
            string strRootPath(rootPath);
#endif
            kv = MMKV::mmkvWithID(strMmapID, (MMKVMode)mode, nullptr, &strRootPath);
        } else {
            kv = MMKV::mmkvWithID(strMmapID, (MMKVMode)mode, nullptr, nullptr);
        }
    }

    return kv;
}

bool setBool_c(MMKV *kv, char *key, bool value)
{
    if (kv && key) {
        string str_key(key);
        return kv->set(value, str_key);
    }
    return false;
}

bool setInt32_t_c(MMKV *kv, char* key, int32_t value)
{
    if (kv && key) {
        string str_key(key);
        return kv->set(value, str_key);
    }
    return false;
}

bool setUint32_t_c(MMKV *kv, char* key, uint32_t value)
{
    if (kv && key) {
        string str_key(key);
        return kv->set(value, str_key);
    }
    return false;
}

bool setInt64_t_c(MMKV *kv, char* key, int64_t value)
{
    if (kv && key) {
        string str_key(key);
        return kv->set(value, str_key);
    }
    return false;
}

bool setUint64_t_c(MMKV *kv, char* key, uint64_t value)
{
    if (kv && key) {
        string str_key(key);
        return kv->set(value, str_key);
    }
    return false;
}

bool setFloat_c(MMKV *kv, char* key, float value)
{
    if (kv && key) {
        string str_key(key);
        return kv->set(value, str_key);
    }
    return false;
}

bool setDouble_c(MMKV *kv, char* key, double value)
{
    if (kv && key) {
        string str_key(key);
        return kv->set(value, str_key);
    }
    return false;
}

bool setString_c(MMKV *kv, char* key, char* value)
{
    if (kv && key) {
        string str_key(key);
        string str_value(value);
        return kv->set(str_value, str_key);
    }
    return false;
}

bool setBytes_c(MMKV *kv, char *key, byteArray *value)
{
    if (kv && key) {
        if (value) {
            MMBuffer buffer(0);
            buffer = MMBuffer(value->data, value->len);
            return kv->set(buffer, key);
        } else {
            kv->removeValueForKey(key);
            return true;
        }
    }

    return false;
}

bool setStringSet_c(MMKV *kv, char *key, DataList *value)
{
    vector<string> keys;

    if (kv && key) {
        if (value) {
            keys.reserve(value->size);
            for (size_t i = 0; i < value->size; i++) {
                string str(value->data[i].charArrayData);
                keys.push_back(str);
            }
            return kv->set(keys, key);
        } else {
            kv->removeValueForKey(key);
            return true;
        }
    }

    return false;
}

bool getBool_c(MMKV *kv, char *key, bool defaultValue)
{
    if (kv && key) {
        string str_key(key);
        return kv->getBool(str_key, defaultValue);
    }
    return defaultValue;
}

int32_t getInt32_c(MMKV *kv, char *key, int32_t defaultValue)
{
    if (kv && key) {
        string str_key(key);
        return kv->getInt32(str_key, defaultValue);
    }
    return defaultValue;
}

uint32_t getUInt32_c(MMKV *kv, char *key, uint32_t defaultValue)
{
    if (kv && key) {
        string str_key(key);
        return kv->getUInt32(str_key, defaultValue);
    }
    return defaultValue;
}

int64_t getInt64_c(MMKV *kv, char *key, int64_t defaultValue)
{
    if (kv && key) {
        string str_key(key);
        return kv->getInt64(str_key, defaultValue);
    }
    return defaultValue;
}

uint64_t getUInt64_c(MMKV *kv, char *key, uint64_t defaultValue)
{
    if (kv && key) {
        string str_key(key);
        return kv->getUInt64(str_key, defaultValue);
    }
    return defaultValue;
}

float getFloat_c(MMKV *kv, char *key, float defaultValue)
{
    if (kv && key) {
        string str_key(key);
        return kv->getFloat(str_key, defaultValue);
    }
    return defaultValue;
}

double getDouble_c(MMKV *kv, char *key, double defaultValue)
{
    if (kv && key) {
        string str_key(key);
        return kv->getDouble(str_key, defaultValue);
    }
    return defaultValue;
}

char *getString_c(MMKV *kv, char *key)
{
    if (kv && key) {
        string str_key(key);
        string str_value;

        if (kv->getString(str_key, str_value)) {
            char *value = (char *)malloc(str_value.length() + 1);
            if (value == NULL) {
                return NULL;
            }

            if (strcpy_s(value, str_value.length() + 1, str_value.c_str()) != 0) {
                free(value);
                return NULL;
            }
            return value;
        }
    }
    return NULL;
}

byteArray *getBytes_c(MMKV *kv, char *key)
{
    if (kv && key) {
        string strKey(key);
        MMBuffer value = kv->getBytes(strKey);
        byteArray *array = (byteArray *)malloc(sizeof(byteArray));
        if (value.length() > 0) {
            array->data = malloc(value.length());
            if (array->data == NULL) {
                return NULL;
            }
            memcpy_s(array->data, value.length(), value.getPtr(), value.length());
            array->len = value.length();
            return array;
        }
    }
    return NULL;
}

static void freeData(DataList *array, size_t cnt)
{
    for (size_t i = 0; i < cnt; i++) {
        free(array->data[i].charArrayData);
    }
}

static DataList *vector2array(const vector<string> &arr)
{
    DataList *array;
    size_t size;

    if (arr.size() == 0) {
        array = (DataList *)malloc(sizeof(DataList));
        if (array == NULL) {
            return NULL;
        }
    } else {
        size = arr.size() + 1;
        array = (DataList *)malloc(size * getSizeofSize_t());
        if (array == NULL) {
            return NULL;
        }
    }

    array->size = arr.size();
    for (size_t i = 0; i < arr.size(); i++) {
        char *cstr = (char *)malloc(arr[i].length() + 1);
        if (strcpy_s(cstr, arr[i].length() + 1, arr[i].c_str()) != 0) {
            free(cstr);
            freeData(array, i);
            free(array);
            return NULL;
        }
        array->data[i].charArrayData = cstr;
    }

    return array;
}

DataList *getStringSet_c(MMKV *kv, char *key)
{
    if (kv && key) {
        string strKey(key);
        vector<string> value;
        bool hasValue = kv->getVector(strKey, value);
        if (hasValue) {
            return vector2array(value);
        }
    }

    return NULL;
}

static vector<string> array2vector(DataList *arrList)
{
    vector<string> keys;
    if (arrList) {
        size_t size = arrList->size;
        keys.reserve(size);
        for (size_t i = 0; i < size; i++) {
            keys.push_back(arrList->data[i].charArrayData);
        }
    }

    return keys;
}

void removeValuesForKeys_c(MMKV *kv, DataList *arrKeys)
{
    if (kv && arrKeys) {
        vector<string> keys = array2vector(arrKeys);
        if (!keys.empty()) {
            kv->removeValuesForKeys(keys);
        }
    }
}

void removeValueForKey_c(MMKV *kv, char *key)
{
    if (kv) {
        kv->removeValueForKey(key);
    }
}

void clearAll_c(MMKV *kv)
{
    if (kv) {
        kv->clearAll();
    }
}

// MMKV's size won't reduce after deleting key-values
// call this method after lots of deleting if you care about disk usage
// note that `clearAll` has the similar effect of `trim`
void trim_c(MMKV *kv)
{
    if (kv) {
        kv->trim();
    }
}

// call this method if the instance is no longer needed in the near future
// any subsequent call to the instance is undefined behavior
void close_c(MMKV *kv)
{
    if (kv) {
        kv->close();
    }
}

void sync_c(MMKV *kv, bool sync)
{
    if (kv) {
        kv->sync((SyncFlag)sync);
    }
}

// call this method if you are facing memory-warning
// any subsequent call to the instance will load all key-values from file again
void clearMemoryCache_c(MMKV *kv)
{
    if (kv) {
        kv->clearMemoryCache();
    }
}

void lock_c(MMKV *kv)
{
    if (kv) {
        kv->lock();
    }
}

void unlock_c(MMKV *kv)
{
    if (kv) {
        kv->unlock();
    }
}

bool tryLock_c(MMKV *kv)
{
    if (kv) {
        return kv->try_lock();
    }

    return false;
}

static MMKVPath_t getString(char *value)
{
#ifdef _WIN32
    return char2wchar(value);
#else
    return value;
#endif
}

// backup one MMKV instance from srcDir to dstDir
// if srcDir is null, then backup from the root dir of MMKV
bool backupOne(char *mmapID, char *dstDir, char *srcDir)
{
    MMKVPath_t strdstDir = getString(dstDir);
    if (srcDir) {
        MMKVPath_t root = getString(srcDir);
        if (root.length() > 0) {
            return MMKV::backupOneToDirectory(mmapID, strdstDir, &root);
        }
    }
    return MMKV::backupOneToDirectory(mmapID, strdstDir);
}

// restore one MMKV instance from srcDir to dstDir
// if dstDir is null, then restore to the root dir of MMKV
bool restoreOne(char *mmapID, char *srcDir, char *dstDir)
{
    MMKVPath_t strSrcDir = getString(srcDir);
    if (dstDir) {
        MMKVPath_t root = getString(dstDir);
        if (root.length() > 0) {
            return MMKV::restoreOneFromDirectory(mmapID, strSrcDir, &root);
        }
    }
    return MMKV::restoreOneFromDirectory(mmapID, strSrcDir);
}

// backup all MMKV instance from srcDir to dstDir
// if srcDir is null, then backup from the root dir of MMKV
// return count of MMKV successfully backuped
size_t backupAll(char *dstDir, char *srcDir)
{
    // historically Android mistakenly use mmapKey as mmapID
    // makes everything tricky with customize root
    MMKVPath_t strdstDir = getString(dstDir);
    if (srcDir) {
        MMKVPath_t root = getString(srcDir);
        if (root.length() > 0) {
            return MMKV::backupAllToDirectory(strdstDir, &root);
        }
    }
    return MMKV::backupAllToDirectory(strdstDir);
}

// restore all MMKV instance from srcDir to dstDir
// if dstDir is null, then restore to the root dir of MMKV
// return count of MMKV successfully restored
size_t restoreAll(char *srcDir, char *dstDir)
{
    // historically Android mistakenly use mmapKey as mmapID
    // makes everything tricky with customize root
    MMKVPath_t strSrcDir = getString(srcDir);
    if (dstDir) {
        MMKVPath_t root = getString(dstDir);
        if (root.length() > 0) {
            return MMKV::restoreAllFromDirectory(strSrcDir, &root);
        }
    }
    return MMKV::restoreAllFromDirectory(strSrcDir);
}

// check if content been changed by other process
void checkContentChanged_c(MMKV *kv)
{
    if (kv) {
        kv->checkContentChanged();
    }
}

// MMKVLogInfo by default
// pass MMKVLogNone to disable all logging
void setLogLevel_c(MMKVLogLevel level)
{
    MMKV::setLogLevel(level);
}

void setCallbackHandler(MMKVHandler handler)
{
    gCallbackHandler = handler;
    if (gCallbackHandler.logHandler) {
        MMKV::registerLogHandler(mmkvLog);
    } else {
        MMKV::unRegisterLogHandler();
    }

    if (gCallbackHandler.onMMKVCRCCheckFail || gCallbackHandler.onMMKVFileLengthError) {
        MMKV::registerErrorHandler(onMMKVError);
    } else {
        MMKV::unRegisterErrorHandler();
    }

    if (gCallbackHandler.onMMKVContentChange) {
        MMKV::registerContentChangeHandler(onMMKVContentChange);
    } else {
        MMKV::unRegisterContentChangeHandler();
    }
}

// detect if the MMKV file is valid or not
// Note: Don't use this to check the existence of the instance,
// the return value is undefined if the file was never created.
bool isFileValid_c(const char *mmapID, char *relatePath)
{
    const string strMmapID(mmapID);
    if (relatePath) {
#ifdef _WIN32
        wstring strRelatePath(char2wchar(relatePath));
#else
        string strRelatePath(relatePath);
#endif
        return MMKV::isFileValid(strMmapID, &strRelatePath);
    } else {
        return MMKV::isFileValid(strMmapID, nullptr);
    }
}

size_t pageSize_c()
{
    return DEFAULT_MMAP_SIZE;
}

const char *version_c()
{
    return MMKV_VERSION;
}

int32_t writeValueToBuffer_c(MMKV *kv, char* key, void *ptr, int32_t size)
{
    if (kv && key && ptr != NULL) {
        string strkey(key);
        return kv->writeValueToBuffer(strkey, ptr, size);
    }
    return -1;
}

size_t getValueSize_c(MMKV *kv, char* key, bool actualSize)
{
    if (kv && key) {
        string strkey(key);
        return kv->getValueSize(strkey, actualSize);
    }
    return 0;
}

bool containsKey_c(MMKV *kv, char* key)
{
    if (kv && key) {
        string strkey(key);
        return kv->containsKey(strkey);
    }
    return false;
}

size_t count_c(MMKV* kv)
{
    if (kv) {
        return kv->count();
    }
    return 0;
}

size_t totalSize_c(MMKV* kv)
{
    if (kv) {
        return kv->totalSize();
    }
    return 0;
}

size_t actualSize_c(MMKV* kv)
{
    if (kv) {
        return kv->actualSize();
    }
    return 0;
}

DataList *allKeys_c(MMKV* kv)
{
    if (kv) {
        vector<string> value = kv->allKeys();
        return vector2array(value);
    }
    return NULL;
}

char* mmapID_c(MMKV* kv)
{
    if (kv) {
        string str_mmid = kv->mmapID();
        char* char_mmid = (char*)malloc(str_mmid.length() + 1);
        if (char_mmid != NULL) {
            if (strcpy_s(char_mmid, str_mmid.length() + 1, str_mmid.c_str()) != 0) {
                free(char_mmid);
                return NULL;
            }
            return char_mmid;
        }
    }
    return NULL;
}

char *cryptKey_c(MMKV* kv)
{
    if (kv) {
        string str_cryptkey = kv->cryptKey();
        char *char_cryptkey = (char*)malloc(str_cryptkey.length() + 1);
        if (char_cryptkey != NULL) {
            if (strcpy_s(char_cryptkey, str_cryptkey.length() + 1, str_cryptkey.c_str()) != 0) {
                free(char_cryptkey);
                return NULL;
            }
            return char_cryptkey;
        }
    }
    return NULL;
}

bool reKey_c(MMKV *kv, char *cryptKey)
{
    if (kv) {
        if (cryptKey) {
            string str_cryptKey0(cryptKey);
            return kv->reKey(str_cryptKey0);
        } else {
            string str_cryptKey1("");
            return kv->reKey(str_cryptKey1);
        }
    }
    return false;
}

void checkReSetCryptKey_c(MMKV *kv, char *cryptKey)
{
    if (kv) {
        if (cryptKey) {
            string str_cryptKey(cryptKey);
            kv->checkReSetCryptKey(&str_cryptKey);
        } else {
            kv->checkReSetCryptKey(NULL);
        }
    }
}

#ifdef __cplusplus
}
#endif